<?php

require_once 'Helper/Form.php';
class Soooner_View_Template
{

    /**
     * 模板参数信息
     *
     * @var array
     */
    private $_options = array();
    public $_massign;

    /**
     * 构造方法
     * @return void
     */
    public function __construct()
    {
        $this->_options = array(
            'template_dir' => TEMPLATE_PATH, //模板文件所在目录
            'cache_dir' => TEMPLATE_CACHE_PATH, //缓存文件存放目录
            'auto_update' => true, //当模板文件改动时是否重新生成缓存
            'cache_lifetime' => 0, //缓存生命周期(秒)，为 0 表示永久
        );
    }

    /**
     * 设定模板参数信息
     *
     * @param  array $options 参数数组
     * @return void
     */
    public function setOptions($options)
    {
        foreach ($options as $name => $value)
        {
        	$this->set($name, $value);
        }
    }

    /**
     * 设定模板参数
     *
     * @param  string $name  参数名称
     * @param  mixed  $value 参数值
     * @return void
     */
    public function set($name, $value)
    {
        switch ($name) {
            case 'template_dir':
                $value = $this->_trimPath($value);
                if (!file_exists($value))
                {
                	throw new Exception('未找到指定的模板目录'.$value);
                }
                $this->_options['template_dir'] = $value;
                break;
            case 'cache_dir':
                $value = $this->_trimPath($value);
                if (!file_exists($value))
                {
                    throw new Exception('未找到指定的缓存目录'.$value);
                }
                $this->_options['cache_dir'] = $value;
                break;
            case 'auto_update':
                $this->_options['auto_update'] = (boolean) $value;
                break;
            case 'cache_lifetime':
                $this->_options['cache_lifetime'] = (float) $value;
                break;
            default:
                throw new Exception('未知的模板配置选项'.$name);
        }
    }

    /**
     * 通过魔术方法设定模板参数
     *
     * @see    Template::set()
     * @param  string $name  参数名称
     * @param  mixed  $value 参数值
     * @return void
     */
    public function __set($name, $value)
    {
        $this->set($name, $value);
    }

    /**
     * 获取模板文件
     *
     * @param  string $file 模板文件名称
     * @return string
     */
    public function getFile($file)
    {
        $cachefile = $this->_getCacheFile($file);
        if (!file_exists($cachefile))
        {
            $this->cache($file);
        }
        return $cachefile;
    }

    /**
     * 获取模板文件
     *
     * @param  string $file 模板文件名称
     * @return string
     */
    public function getIncludeFile($file)
    {
        $cachefile = $this->_getCacheFile($file);
        if (!file_exists($cachefile))
        {
        	$withexit = false;
            $this->cache($file, $withexit);
        }
        return $cachefile;
    }

    /**
     * 检测模板文件是否需要更新缓存
     *
     * @param string $file     模板文件名称
     * @param string $md5data  模板文件 md5 校验信息
     * @param integer $md5data 模板文件到期时间校验信息
     * @return void
     */
    public function check($file, $md5data, $expireTime)
    {
        if ($this->_options['auto_update']
        && md5_file($this->_getTplFile($file)) != $md5data)
        {
        	$this->cache($file);
        }
        if ($this->_options['cache_lifetime'] != 0
        && (time() - $expireTime >= $this->_options['cache_lifetime']))
        {
        	$this->cache($file);
        }
    }

    public function soooner_view_help_callback($matches)
    {
//        eval('$param = '.$matches[2].';');
        $matches[2] = str_replace('$','<!--$-->',$matches[2]);
        $param = preg_replace("/\s+/",", ",$matches[2]);
        $param = str_replace('*', 'null', $param);

        return '<?=Soooner_View_Helper_Form::' . $matches[1] . '(' . $param . ')?>';
//        $matches[2] = str_replace('$','',$matches[2]);
//        eval('$param = $this->_massign[\''.$matches[2].'\'];');
//        return call_user_func('Soooner_View_Helper_Form::' . $matches[1], $param);
    }

    /**
     * 对模板文件进行缓存
     *
     * @param  string  $file    模板文件名称
     * @return void
     */
    public function cache($file, $withexit=true)
    {
        $tplfile = $this->_getTplFile($file);

        if (!is_readable($tplfile))
        {
        	throw new Exception('模板文件 '.$tplfile.'未找到或者无法打开');
        }

        //取得模板内容
        $template = file_get_contents($tplfile);

        //过滤 <!--{}-->
        $template = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}",
            $template);

        //替换语言包变量
        //$template = preg_replace("/\{lang\s+(.+?)\}/ies",
        //   "languagevar('\\1')", $template);

        //替换 PHP 换行符
        $template = str_replace("{LF}", "<?=\"\\n\"?>", $template);

        //替换form变量
        $template = preg_replace_callback("/\{form\s+(\S+)\s+([^}]+)\}/is",
            array($this, 'soooner_view_help_callback'), $template);
        //$template = preg_replace_callback("/\{form\s+(\S+)\s+(\S+)\}/is",
        //    array($this, 'soooner_view_help_callback'), $template);

        //替换直接变量输出
        $varRegexp = "((\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)"
                    . "(\[[a-zA-Z0-9_\-\.\"\'\$\x7f-\xff]+\])*)";
//        $varRegexp = "((\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)"
//                    . "(\[[a-zA-Z0-9_\-\.\"\'\[\]\$\x7f-\xff]+\])*)";
        $template =
            preg_replace("/\{(\\\$[a-zA-Z0-9_\[\]\'\"\$\.\x7f-\xff]+)\}/s",
            "<?=\\1?>", $template);
        $template = preg_replace("/$varRegexp/es", "self::addquote('<?=\\1?>')",
            $template);
        $template = preg_replace("/\<\?\=\<\?\=$varRegexp\?\>\?\>/es",
            "self::addquote('<?=\\1?>')", $template);

        //替换 PHP $变量
        $template = str_replace('<!--$-->', '$', $template);
        $template = str_replace('*$*', '$', $template);
        $template = str_replace('*{*', '{', $template);
        $template = str_replace('*}*', '}', $template);

        //替换模板载入命令
        $template = preg_replace(
            "/[\n\r\t]*\{template\s+([a-z0-9_]+)\}[\n\r\t]*/is",
            "\r\n<? include(\$this->_dzTemplate->getIncludeFile('\\1')); ?>\r\n",
            $template
        );
        $template = preg_replace(
            "/[\n\r\t]*\{template\s+(.+?)\}[\n\r\t]*/is",
            "\r\n<? include(\$this->_dzTemplate->getIncludeFile(\\1)); ?>\r\n",
            $template
        );

        //替换特定函数
        $template = preg_replace(
            "/[\n\r\t]*\{eval\s+(.+?)\}[\n\r\t]*/ies",
            "self::stripvtags('<? \\1 ?>','')",
            $template
        );
        $template = preg_replace(
            "/[\n\r\t]*\{echo\s+(.+?)\}[\n\r\t]*/ies",
            "self::stripvtags('<? echo \\1; ?>','')",
            $template
        );
        $template = preg_replace(
            "/([\n\r\t]*)\{elseif\s+(.+?)\}([\n\r\t]*)/ies",
            "self::stripvtags('\\1<? } elseif(\\2) { ?>\\3','')",
            $template
        );
        $template = preg_replace(
            "/([\n\r\t]*)\{else\}([\n\r\t]*)/is",
            "\\1<? } else { ?>\\2",
            $template
        );

        //替换循环函数及条件判断语句
        $nest = 5;
        for ($i = 0; $i < $nest; $i++)
        {
            $template = preg_replace(
                "/[\n\r\t]*\{loop\s+(\S+)\s+(\S+)\}[\n\r]*(.+?)[\n\r]*\{\/loop\}[\n\r\t]*/ies",
                "self::stripvtags('<? if(is_array(\\1)) { foreach(\\1 as \\2) { ?>','\\3<? } } ?>')",
                $template
            );
            $template = preg_replace(
                "/[\n\r\t]*\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}[\n\r\t]*(.+?)[\n\r\t]*\{\/loop\}[\n\r\t]*/ies",
                "self::stripvtags('<? if(is_array(\\1)) { foreach(\\1 as \\2 => \\3) { ?>','\\4<? } } ?>')",
                $template
            );
            $template = preg_replace(
                "/([\n\r\t]*)\{if\s+(.+?)\}([\n\r]*)(.+?)([\n\r]*)\{\/if\}([\n\r\t]*)/ies",
                "self::stripvtags('\\1<? if(\\2) { ?>\\3','\\4\\5<? } ?>\\6')",
                $template
            );
        }

        //检查权限
        $template = preg_replace(
            "/[\n\r\t]*\{checkallow\s+(\S+)\s+(\S+)\}[\n\r]*(.+?)[\n\r]*\{\/checkallow\}[\n\r\t]*/ies",
            "self::stripvtags('<? if (\$this->checkAllow(\'\\1\',\'\\2\')) { ?>','\\3<? } ?>')",
            $template
        );

        //常量替换
        $template = preg_replace(
            "/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/s",
            "<?=\\1?>",
            $template
        );

        //删除 PHP 代码断间多余的空格及换行
        $template = preg_replace("/ \?\>[\n\r]*\<\? /s", " ", $template);

        //其他替换
        $template = preg_replace(
            "/\"(http)?[\w\.\/:]+\?[^\"]+?&[^\"]+?\"/e",
            "self::transamp('\\0')",
            $template
        );
        $template = preg_replace(
            "/\<script[^\>]*?src=\"(.+?)\".*?\>\s*\<\/script\>/ise",
            "self::stripscriptamp('\\1')",
            $template
        );
        $template = preg_replace(
            "/[\n\r\t]*\{block\s+([a-zA-Z0-9_]+)\}(.+?)\{\/block\}/ies",
            "self::stripblock('\\1', '\\2')",
            $template
        );
        $template = str_replace('*!*', '', $template);

        //添加 md5 及过期校验
        $md5data = md5_file($tplfile);
        $expireTime = time();
        //SMARTY_DIR     . "\$template->check('$file', '$md5data', $expireTime);"
        /* $template = "<? if (!class_exists('DzTemplate')) die('Access Denied');"*/
        $template = "<?php if (!defined('TEMPLATE_CACHE_PATH')) die('Access Denied');\r\n"
                  . "\$this->_dzTemplate->check('$file', '$md5data', $expireTime);"
                  . "?>\r\n$template";

        if ($withexit)
        {
//          $template .= "\r\n<?php exit();?".">";
//          $template .= "\r\n?".">";
        }

        //写入缓存文件
        $cachefile = $this->_getCacheFile($file);
        $makepath = $this->_makePath($cachefile);
        if ($makepath !== true)
        {
            throw new Exception('无法创建缓存目录'.$makepath);
        }
        #  file_put_contents($cachefile, $template);
        $this->_writeFile($cachefile, $template);
    }

    /**
     * 将路径修正为适合操作系统的形式
     *
     * @param  string $path 路径名称
     * @return string
     */
    private function _trimPath($path)
    {
        return str_replace(array('/', '\\', '//', '\\\\'), '/', $path);
    }

    /**
     * 获取模板文件名及路径
     *
     * @param  string $file 模板文件名称
     * @return string
     */
    private function _getTplFile($file)
    {
        return $this->_trimPath($this->_options['template_dir'] .'/' . $file);
    }

    /**
     * 获取模板缓存文件名及路径
     *
     * @param  string $file 模板文件名称
     * @return string
     */
    private function _getCacheFile($file)
    {
        $file = preg_replace('/\.[a-z0-9\-_]+$/i', '.cache.php', $file);
        return $this->_trimPath($this->_options['cache_dir'] . '/' . $file);
    }

    /**
     * 根据指定的路径创建不存在的文件夹
     *
     * @param  string  $path 路径/文件夹名称
     * @return string
     */
    private function _makePath($path)
    {
        $dirs = explode('/', dirname($this->_trimPath($path)));
        $tmp = '';
        foreach ($dirs as $dir)
        {
            $tmp .= $dir . '/';
            if (!file_exists($tmp) && !@mkdir($tmp, 0777))
                return $tmp;
        }
        return true;
    }

    /**
     * 写文件
     * 此函数修复了在大数据刷新，出现缓存破坏的bug
     * @return intager 写入数据的字节数
     */
    private function _writeFile($filename, $content, $mode = 'rb+')
    {
        $length = strlen($content);
        @touch($filename);
        if (!is_writeable($filename))
        {
            @chmod($filename, 0666);
        }

        if (($fp = @fopen($filename, $mode)) === false)
        {
            trigger_error('_writeFile() failed to open stream: Permission denied', E_USER_WARNING);
            return false;
        }

        flock($fp, LOCK_EX | LOCK_NB);

        $bytes = 0;
        if (($bytes = @fwrite($fp, $content)) === false)
        {
            $errormsg = sprintf('_writeFile() Failed to write %d bytes to %s', $length, $filename);
            trigger_error($errormsg, E_USER_WARNING);
            return false;
        }

        if ($mode == 'rb+')
        {
            @ftruncate($fp, $length);
        }

        @fclose($fp);

        //检查是否写入了所有的数据
        if ($bytes != $length)
        {
            $errormsg = sprintf('_writeFile() Only %d of %d bytes written, possibly out of free disk space.', $bytes, $length);
            trigger_error($errormsg, E_USER_WARNING);
            return false;
        }

        //返回长度
        return $bytes;
    }

    public static function transamp($template)
    {
        $template = str_replace('&', '&amp;', $template);
        $template = str_replace('&amp;amp;', '&amp;', $template);
        $template = str_replace('\"', '"', $template);
        return $template;
    }

    public static function stripvtags($expr, $statement)
    {
        $expr = str_replace("\\\"", "\"", preg_replace("/\<\?\=(\\\$.+?)\?\>/s", "\\1", $expr));
        $statement = str_replace("\\\"", "\"", $statement);
        return $expr . $statement;
    }

    public static function addquote($var)
    {
        return str_replace("\\\"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
    }

    public static function stripscriptamp($s)
    {
        $s = str_replace('&amp;', '&', $s);
        return "<script src=\"$s\" type=\"text/javascript\"></script>";
    }

    public static function stripblock($var, $s)
    {
        $s = str_replace('\\"', '"', $s);
        $s = preg_replace("/<\?=\\\$(.+?)\?>/", "{\$\\1}", $s);
        preg_match_all("/<\?=(.+?)\?>/e", $s, $constary);
        $constadd = '';
        $constary[1] = array_unique($constary[1]);
        foreach($constary[1] as $const)
        {
            $constadd .= '$__' . $const  .' = ' . $const . ';';
        }
        $s = preg_replace("/<\?=(.+?)\?>/", "{\$__\\1}", $s);
        $s = str_replace('?>', "\n\$$var .= <<<EOF\n", $s);
        $s = str_replace('<?', "\nEOF;\n", $s);
        return "<?\n$constadd\$$var = <<<EOF\n" . $s . "\nEOF;\n?>";
    }

}
